﻿using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data;
using System.Data.Objects;
using System.Data.Objects.DataClasses;

namespace Dispatcher.Extensions
{
    /// <summary>
    /// Object set extensions.
    /// </summary>
    public static class ObjectSetExtensions
    {
        /// <summary>
        /// Contains observable collections of object sets.
        /// </summary>
        private static readonly Dictionary<object, object> ObjectSetItems = new Dictionary<object, object>();

        /// <summary>
        /// Contains observable collections of object sets.
        /// </summary>
        private static readonly Dictionary<object, object> ObjectSetDeletedItems = new Dictionary<object, object>();

        /// <summary>
        /// Indicate observable collection refresh operation.
        /// </summary>
        private static bool _isRefresh;

        /// <summary>
        /// Refreshes the specified object set.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <param name="objectSet">The object set.</param>
        public static void Refresh<TEntity>(this ObjectSet<TEntity> objectSet)
            where TEntity : EntityObject
        {
            if (!ObjectSetItems.ContainsKey(objectSet))
            {
                InitializeObservableCollections(objectSet);
            }

            var items = (ObservableCollection<TEntity>)ObjectSetItems[objectSet];
            var deletedItems = (ObservableCollection<TEntity>)ObjectSetDeletedItems[objectSet];
            deletedItems.Clear();
            items.Clear();
            _isRefresh = true;
            items.AddRange(objectSet);
            _isRefresh = false;
        }
        
        /// <summary>
        /// Adds the back deleted items.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <param name="objectSet">The object set.</param>
        public static void AddBackDeletedItems<TEntity>(this ObjectSet<TEntity> objectSet) where TEntity : EntityObject
        {
            if (!ObjectSetItems.ContainsKey(objectSet))
            {
                InitializeObservableCollections(objectSet);
            }

            var items = (ObservableCollection<TEntity>)ObjectSetItems[objectSet];
            var deletedItems = (ObservableCollection<TEntity>)ObjectSetDeletedItems[objectSet];
            items.AddRange(deletedItems);
            deletedItems.Clear();
        }

        /// <summary>
        /// Ases the observable collection.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <param name="objectSet">The object set.</param>
        /// <returns>Object set items as observable collection.</returns>
        public static ObservableCollection<TEntity> ItemsAsObservableCollection<TEntity>(this ObjectSet<TEntity> objectSet)
            where TEntity : EntityObject
        {
            if (!ObjectSetItems.ContainsKey(objectSet))
            {
                InitializeObservableCollections(objectSet);
            }

            return ObjectSetItems[objectSet] as ObservableCollection<TEntity>;
        }

        /// <summary>
        /// Deleteds the items as observable collection.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <param name="objectSet">The object set.</param>
        /// <returns>Object set deleted items as observable collection.</returns>
        public static ObservableCollection<TEntity> DeletedItemsAsObservableCollection<TEntity>(this ObjectSet<TEntity> objectSet) 
            where TEntity : EntityObject
        {
            if (!ObjectSetDeletedItems.ContainsKey(objectSet))
            {
                InitializeObservableCollections(objectSet);
            }

            return ObjectSetDeletedItems[objectSet] as ObservableCollection<TEntity>;
        }

        /// <summary>
        /// Initializes the observable collections.
        /// </summary>
        /// <typeparam name="TEntity">The type of the entity.</typeparam>
        /// <param name="objectSet">The object set.</param>
        private static void InitializeObservableCollections<TEntity>(ObjectSet<TEntity> objectSet)
            where TEntity : EntityObject
        {
            var itemsObservableCollection = new ObservableCollection<TEntity>(objectSet);
            var deletedItemsObservableCollection = new ObservableCollection<TEntity>();
            ObjectSetDeletedItems.Add(objectSet, deletedItemsObservableCollection);
            ObjectSetItems.Add(objectSet, itemsObservableCollection);
            itemsObservableCollection.CollectionChanged +=
                (o, args) =>
                    {
                        if (args.OldItems != null)
                        {
                            foreach (TEntity item in args.OldItems)
                            {
                                objectSet.DeleteObject(item);
                                deletedItemsObservableCollection.Add(item);
                            }
                        }

                        if (args.NewItems != null && !_isRefresh)
                        {
                            foreach (TEntity item in args.NewItems)
                            {
                                if (item.EntityState == EntityState.Deleted)
                                {
                                    objectSet.Detach(item);
                                }

                                objectSet.AddObject(item);
                            }
                        }
                    };
        }
    }
}
